/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.redis.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.iwindplus.boot.domain.constant.CommonConstant.SymbolConstant;
import com.iwindplus.boot.domain.context.UserContextHolder;
import com.iwindplus.boot.domain.enums.BizCodeEnum;
import com.iwindplus.boot.domain.exception.BizException;
import com.iwindplus.boot.domain.vo.UserBaseVO;
import com.iwindplus.boot.redis.domain.annotation.RedisLock;
import com.iwindplus.boot.redis.domain.constant.RedisConstant;
import com.iwindplus.boot.redis.domain.property.RedisProperty;
import com.iwindplus.boot.redis.manager.RedissonManager;
import com.iwindplus.boot.util.DatesUtil;
import com.iwindplus.boot.util.ExpressionUtil;
import jakarta.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.StringUtils;

/**
 * 分布式锁切面.
 *
 * @author zengdegui
 * @since 2024/07/06 12:18
 */
@Slf4j
@Aspect
public class RedisLockAspect {

    @Resource
    private KeyGenerator keyGenerator;

    @Resource
    private CacheProperties cacheProperties;

    @Resource
    private RedissonManager redissonManager;

    @Resource
    private RedisProperty property;

    @Pointcut("@within(com.iwindplus.boot.redis.domain.annotation.RedisLock) || @annotation(com.iwindplus.boot.redis.domain.annotation.RedisLock)")
    private void pointCutMethod() {
    }

    /**
     * 环绕切面.
     *
     * @param joinPoint joinPoint
     * @return Object
     * @throws Throwable
     */
    @Around("pointCutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        if (Boolean.FALSE.equals(this.property.getLock().getEnabled()) || Boolean.FALSE.equals(redisLock.enabled())) {
            return joinPoint.proceed();
        }

        long beginMillis = System.currentTimeMillis();
        log.info("Redis分布式锁获取，开始时间={}", DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));

        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        String lockKey = this.getLockKey(target, method, args, redisLock);

        log.info("Redis分布式锁获取，lockKey={}，开始时间={}", lockKey, DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));
        return this.getResult(joinPoint, redisLock, beginMillis, lockKey);
    }

    private Object getResult(ProceedingJoinPoint joinPoint, RedisLock redisLock, long beginMillis, String lockKey) throws Throwable {
        RLock lock = null;
        try {
            lock = redissonManager.getLock(redisLock.lockType(), lockKey);

            boolean status = redissonManager.tryLock(lock, redisLock.waitTime(), redisLock.leaseTime(), redisLock.timeUnit());
            if (!status) {
                log.warn("Redis分布式锁获取失败，lockKey={}, cost={}ms", lockKey, System.currentTimeMillis() - beginMillis);
                throw new BizException(BizCodeEnum.GET_LOCK_ERROR, new Object[]{lockKey});
            }

            // 执行上锁方法
            return joinPoint.proceed();
        } finally {
            redissonManager.unlock(lock);

            final long endMillis = System.currentTimeMillis();
            log.info("Redis分布式锁释放完成，lockKey={}，结束时间={}，总执行毫秒数={}", lockKey,
                DatesUtil.parseDate(endMillis, DatePattern.NORM_DATETIME_MS_PATTERN), endMillis - beginMillis);
        }
    }

    private String getLockKey(Object target, Method method, Object[] args, RedisLock redisLock) {
        StringBuilder sb = new StringBuilder();
        CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
        if (StringUtils.hasText(redisProperties.getKeyPrefix())) {
            sb.append(redisProperties.getKeyPrefix());
        }
        sb.append(RedisConstant.LOCK_KEY_PREFIX);
        if (redisLock.useUserId()) {
            UserBaseVO context = UserContextHolder.getContext();
            if (ObjectUtil.isNotEmpty(context)) {
                sb.append(context.getUserId()).append(SymbolConstant.COLON);
            }
        }
        String[] names = redisLock.names();
        if (ArrayUtil.isNotEmpty(names)) {
            String name = Arrays.asList(names).stream().collect(Collectors.joining(SymbolConstant.UNDERLINE));
            sb.append(name).append(SymbolConstant.COLON);
        }
        // 检验是否需要生成默认的key
        String[] keys = redisLock.keys();
        if (ArrayUtil.isNotEmpty(keys)) {
            final List<Object> resultList = ExpressionUtil.parse(method, args, keys, Object.class);
            if (CollUtil.isNotEmpty(resultList)) {
                final String str = resultList.stream().map(Object::toString).collect(Collectors.joining(SymbolConstant.UNDERLINE));
                sb.append(str);
                return sb.toString();
            }
        }
        final String str = this.keyGenerator.generate(target, method, args).toString();
        sb.append(str);
        return sb.toString();
    }
}
